import copy
import logging
import re
from pathlib import Path
from string import Template

import allure
import jsonpath
import yaml

from lib import write_yaml, read_yaml_all
from lib.core.hot_load import HotLoad
from lib.setting.setting import TEMP_FILE, BASE_PATH

logger = logging.getLogger(__name__)


@allure.step('解析YAML中的extract关键字，提取response中的响应数据')
def extract(response, var_name, attr_name, expr: str, index):
    """
    解析yaml中的extract键值
    :param response: 相应对象
    :param var_name: 取yaml中的那个值。例如：csrf_token
    :param attr_name: 我们要使用response中的那个属性。例如：json，表示取response.json()方法
    :param expr: 表达式，默认通过jsonpath取值。例如：$.token
    :param index: 索引下标，若执行表达式，结果返回值类型为列表，则取第几个值
    :return:
    """
    # 深拷贝，复制值。浅拷贝：复制地址。
    res = copy.deepcopy(response)
    # 将.json()方法，转为属性
    try:
        res.json = res.json()
    except Exception:
        res.json = {'msg': 'Response not json data'}
    # 通过反射获取值
    data = getattr(res, attr_name)
    if expr.startswith('$'):
        # 说明是jsonpath
        lst = jsonpath.jsonpath(data, expr)
    else:
        # 正则表达式
        lst = re.findall(expr, data)
    # 通过下标，获取值
    if lst:
        # print(f'{var_name},{lst[index]}')
        write_yaml(Path(BASE_PATH).joinpath(TEMP_FILE), {var_name: lst[index]})


# 接卸使用变量，把${access_token},替换从extract.yaml里面提取
@allure.step('for: 执行use_extract_value方法,替换request属性的热加载函数')
def use_extract_value(request_data: dict):
    # 1. 将字典转为yaml（字符串）
    data_str = yaml.safe_dump(request_data)
    # 2.1 Template可以使用字典方式进行替换，因此读取yaml中所有的数据，进行替换
    # new_data = Template(data_str).safe_substitute(read_yaml_all(r'D:\Test\python\ry-auto-api-test\temp\extract.yaml'))

    # 2.2 Template()模板的形式，无法解决热加载函数的问题，因此我们在此处定义热加载函数模板，原本的${KEY}的方式也支持
    logger.debug('for: use_extract_value()方法内,执行hot_load_replace()方法')
    new_data = hot_load_replace(data_str)
    # 3. 将yaml再转为字典数据，并返回
    return yaml.safe_load(new_data)


@allure.step('for: use_extract_value()方法内,执行hot_load_replace()方法')
def hot_load_replace(data):
    """
    判定是否存在：热加载函数
    热加载，替换python函数方法。
    :param data: yaml 信息
    :return:
    """
    # 1. 考虑yaml中使用热加载函数【read_yaml(loginName)】,意思是：读取extract.yaml,获取loginName对应的值
    # 2. 正则表达式匹配热加载函数
    # 3. 正则表达式（.*?）,匹配所有值，可能多个，也可能一个
    regexp = "\\$\\{(.*?)\\((.*?)\\)\\}"
    lst = re.findall(regexp, data)
    # 防止出现${key}的形式
    yaml_data = read_yaml_all(Path(BASE_PATH).joinpath(TEMP_FILE))
    if yaml_data:
        logger.debug('for: hot_load_replace()方法,执行Template()模板替换方法，提取参数')
        data = Template(data).safe_substitute(yaml_data)

    for i in lst:
        if i[1] == '':
            # 为''，表示热加载函数不需要参数值
            # !!!!!,()：这个地方我们是使用反射，调用方法，因此需要添加()
            values = getattr(HotLoad(), i[0])()
        else:
            # 表示热加载函数的参数值，可能存在多个
            params = i[1].split(',')
            values = getattr(HotLoad(), i[0])(*params)
        if isinstance(values, str) and values.isdigit():
            # 如果是str类型，并且是数字，则需要进行
            values = f'"{values}"'
        elif isinstance(values, int):
            # 如果是str类型，并且是数字，则需要进行
            values = str(values)
        # yaml中的热加载函数写法
        hot_load_fun = '${' + f'{i[0]}({i[1]})' + '}'
        logger.debug(f'for: 热加载函数通过反射，替换YAML中:[{hot_load_fun}]为[{values}]')
        data = data.replace(hot_load_fun, values)
        # print(data)
    return data
